/*
 * Authors: Filipe Ferraz Salgado  (filipesalgado@hotmail.com)
 *          Alexandre Albano       (aalbano@gmail.com)
 *          Instituto de Matemática e Estatística
 *          Universidade de São Paulo
 */

package br.org.groupware_workbench.commonalgorithms.analisadortextual_1_1;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.apache.lucene.analysis.br.BrazilianAnalyzer;

import br.org.groupware_workbench.commonalgorithms.analisadortextual_1_1.com.alag.ci.textanalysis.lucene.impl.TagCacheImpl;
import br.org.groupware_workbench.commonalgorithms.analisadortextual_1_1.com.alag.ci.tagcloud.TagCloud;
import br.org.groupware_workbench.commonalgorithms.analisadortextual_1_1.com.alag.ci.tagcloud.TagCloudElement;
import br.org.groupware_workbench.commonalgorithms.analisadortextual_1_1.com.alag.ci.tagcloud.impl.TagCloudElementImpl;
import br.org.groupware_workbench.commonalgorithms.analisadortextual_1_1.com.alag.ci.tagcloud.impl.TagCloudImpl;
import br.org.groupware_workbench.commonalgorithms.analisadortextual_1_1.com.alag.ci.textanalysis.InverseDocFreqEstimator;
import br.org.groupware_workbench.commonalgorithms.analisadortextual_1_1.com.alag.ci.textanalysis.TagMagnitude;
import br.org.groupware_workbench.commonalgorithms.analisadortextual_1_1.com.alag.ci.textanalysis.TagMagnitudeMatrix;
import br.org.groupware_workbench.commonalgorithms.analisadortextual_1_1.com.alag.ci.textanalysis.TagMagnitudeVector;
import br.org.groupware_workbench.commonalgorithms.analisadortextual_1_1.com.alag.ci.textanalysis.TextAnalyzer;
import br.org.groupware_workbench.commonalgorithms.analisadortextual_1_1.com.alag.ci.textanalysis.lucene.impl.EqualInverseDocFreqEstimator;
import br.org.groupware_workbench.commonalgorithms.analisadortextual_1_1.com.alag.ci.textanalysis.lucene.impl.InverseDocFreqEstimatorImpl;
import br.org.groupware_workbench.commonalgorithms.analisadortextual_1_1.com.alag.ci.textanalysis.lucene.impl.LuceneTextAnalyzer;
import br.org.groupware_workbench.commonalgorithms.analisadortextual_1_1.com.alag.ci.textanalysis.termvector.impl.TagMagnitudeMatrixImpl;
import br.org.groupware_workbench.commonalgorithms.analisadortextual_1_1.exception.AnalisadorTextualException;

import br.org.groupware_workbench.commonalgorithms.analisadortextual_1_1.com.alag.ci.tagcloud.impl.HTMLTagCloudDecorator;
import br.org.groupware_workbench.commonalgorithms.analisadortextual_1_1.com.alag.ci.tagcloud.impl.LinearFontSizeComputationStrategy;

public class AnalisadorTextual {
	private List<String> listaFrases;
	private Map<String, List<String>> mapPalavrasSinonimos;

	private List<String> stopWords = new LinkedList<String>();
		
	/**
	 * Construtor da classe. Cria um analisador de textos que permite a extra��o dos termos 
	 * 	mais relevantes.
	 * @param mapPalavraSinonimos - Map que relaciona uma <code>String</code> com uma poss�vel 
	 * 	lista de sin�nimos dela.
	 * @param listaFrases - Lista de <code>String</code> que devem ser consideradas frases 
	 * 	durante a an�lise do texto.
	 */
	public AnalisadorTextual(Map<String, List<String>> mapPalavraSinonimos, 
			List<String> listaFrases) {
		this.mapPalavrasSinonimos = mapPalavraSinonimos;
		this.listaFrases = listaFrases;
		inicializaStopWords();
	}

	/**
	 * Construtor da classe. Cria um analisador de textos que permite a extra��o dos termos 
	 * 	mais relevantes.
	 * @param mapPalavraSinonimos - Map que relaciona uma <code>String</code> com uma poss�vel 
	 * 	lista de sin�nimos dela.
	 * @param listaFrases - Lista de <code>String</code> que devem ser consideradas frases 
	 * 	durante a an�lise do texto.
	 * @param stopWords - Lista de <code>String</code> que ser�o utilizadas como "Stop Words" 
	 * 	 (ou seja, ignoradas).
	 */
	public AnalisadorTextual(Map<String, List<String>> mapPalavraSinonimos, 
			List<String> listaFrases, LinkedList<String> stopWords) {
		this.mapPalavrasSinonimos = mapPalavraSinonimos;
		this.listaFrases = listaFrases;
		this.stopWords = stopWords;
	}
	
	public AnalisadorTextual() { 
		this.mapPalavrasSinonimos = new HashMap<String, List<String>>();
		this.listaFrases = new LinkedList<String>();
		inicializaStopWords();
	}

	private TextAnalyzer criaAnalisador() {
		TagCacheImpl tagCache = new TagCacheImpl(stopWords.toArray(new String[0]));
		InverseDocFreqEstimator idfEstimator =  new EqualInverseDocFreqEstimator();
		TextAnalyzer lta = new LuceneTextAnalyzer(tagCache, idfEstimator, 
				this.mapPalavrasSinonimos, this.listaFrases, stopWords);
		return lta;
	}
	
	private TextAnalyzer criaAnalisadorIDF(int numdocs) {
		TagCacheImpl tagCache = new TagCacheImpl(stopWords.toArray(new String[0]));
		InverseDocFreqEstimator idfEstimator =  new InverseDocFreqEstimatorImpl(numdocs);
		TextAnalyzer lta = new LuceneTextAnalyzer(tagCache, idfEstimator, 
				this.mapPalavrasSinonimos, this.listaFrases, stopWords);
		return lta;
	}
	
	/**
	 * Analisa o texto passado como par�metro, extraindo os termos mais relevantes levando 
	 * em conta a lista de stopwords e o stemmer (lematizador) atuais.
	 * Tamb�m � utilizada a lista de frases atual na an�lise. <br/>
	 * <code>TagMagnitudeVector tmv = new AnalisadorTextual().analisar("frase que ser� analisada.");<br/>
	 *       System.out.println("Vetor de pesos: " + tmv); </code>
	 * 
	 * @param texto - String que cont�m um texto que ser� analisado.
	 * @return Um objeto <code>Tags</code> que representa um vetor com os termos extra�dos do texto e 
	 * seus respectivos pesos.
	 * @throws AnalisadorTextualException Se houver algum erro de IO na leitura do texto passado.
	 *  
	 */
	public TagMagnitudeVector analisar(String texto) throws AnalisadorTextualException {
		TextAnalyzer lta = criaAnalisador();

		TagMagnitudeVector tmv;
		try {
			tmv = lta.createTagMagnitudeVector(texto);
		} catch (IOException e) {
			throw new AnalisadorTextualException(e);
		}

		return tmv;
	}

	/**
	 * Analisa o texto passado como par�metro, id�ntico a <code>analisar(String texto)</code>,
	 * por�m, neste m�todo os pesos da descric�o curta influenciam mais o vetor de pesos retornado.
	 * <code>TagMagnitudeVector tmv = new AnalisadorTextual().analisar("Posse do presidente dos Estados Unidos", "Artigo razoavelmente comprido descrevendo o acontecimento......");<br/>
	 *       System.out.println("Vetor de pesos: " + tmv); </code>
	 * 
	 * @param descricaoCurta - String que cont�m uma descric�o curta do texto que ser� analisado.
	 * @param texto - String que cont�m um texto que ser� analisado.
	 * @return Um objeto <code>Tags</code> que representa um vetor com os termos extra�dos do texto e 
	 * seus respectivos pesos.
	 * @throws AnalisadorTextualException Se houver algum erro de IO na leitura do texto passado.
	 *  
	 */
	
	public TagMagnitudeVector analisar(String descricaoCurta, String texto) throws AnalisadorTextualException {
		TextAnalyzer lta = criaAnalisador();	

		TagMagnitudeVector tmv1;
		TagMagnitudeVector tmv2;

		try {
			tmv1 = lta.createTagMagnitudeVector(descricaoCurta);
			tmv2 = lta.createTagMagnitudeVector(texto);
		} catch (IOException e) {
			throw new AnalisadorTextualException(e);
		}

		TagMagnitudeVector resultado = tmv1.add(tmv2);
		return resultado;
	}

	/**
	 * Analisa o texto passado como par�metro, id�ntico a <code>analisar(String texto)</code>,
	 * por�m, neste m�todo, os termos que ocorrem em outrosTextos influenciam negativamente nos pesos dos termos da String "texto".
	 * <code>TagMagnitudeVector tmv = new AnalisadorTextual().analisar("Artigo razoavelmente comprido descrevendo o acontecimento......", listaDeTodasAsOutrasNoticias);<br/>
	 *       System.out.println("Vetor de pesos: " + tmv); </code>
	 * 
	 * @param texto - String que cont�m um texto que ser� analisado.
	 * @param outrosTextos - Lista de Strings que cont�m outros textos para c�lculo de IDF.
	 * @return Um objeto <code>Tags</code> que representa um vetor com os termos extra�dos do texto e 
	 * seus respectivos pesos.
	 * @throws AnalisadorTextualException Se houver algum erro de IO na leitura do texto passado.
	 *  
	 */
	
	public TagMagnitudeVector analisar(String texto, List<String> outrosTextos) throws AnalisadorTextualException {
		TextAnalyzer lta = criaAnalisadorIDF(outrosTextos.size() + 1);
		TagMagnitudeVector tmv = null;
		try {
			for(String outroTexto : outrosTextos) {
				lta.createTagMagnitudeVector(outroTexto);
			}

			tmv = lta.createTagMagnitudeVector(texto);		
		} catch (IOException e) {
			throw new AnalisadorTextualException(e);
		}

		return tmv;
	}

	/**
	 * Analisa o texto passado como par�metro, id�ntico a <code>analisar(String descricaoCurta, String texto)</code>,
	 * por�m, neste m�todo, os termos que ocorrem em outrosTextos influenciam negativamente nos pesos dos termos da String "texto".
	 * <code>TagMagnitudeVector tmv = new AnalisadorTextual().analisar("Posse do presidente dos Estados Unidos", "Artigo razoavelmente comprido descrevendo o acontecimento......", listaDeTodasAsOutrasNoticias);<br/>
	 *       System.out.println("Vetor de pesos: " + tmv); </code>
	 * 
	 * @param descricaoCurta - String que cont�m uma descric�o curta do texto que ser� analisado.
	 * @param texto - String que cont�m um texto que ser� analisado.
	 * @param outrosTextos - Lista de Strings que cont�m outros textos para c�lculo de IDF.
	 * @return Um objeto <code>Tags</code> que representa um vetor com os termos extra�dos do texto e 
	 * seus respectivos pesos.
	 * @throws AnalisadorTextualException Se houver algum erro de IO na leitura do texto passado.
	 *  
	 */
	
	public TagMagnitudeVector analisar(String descricaoCurta, String texto, List<String> outrosTextos) throws AnalisadorTextualException {
		TextAnalyzer lta = criaAnalisadorIDF(outrosTextos.size() + 1);
		
		TagMagnitudeVector tmv = null;
		try {
			for(String outroTexto : outrosTextos) {
				lta.createTagMagnitudeVector(outroTexto);
			}

			tmv = lta.createTagMagnitudeVector(texto);
		} catch (IOException e) {
			throw new AnalisadorTextualException(e);
		}

		TextAnalyzer lta2 = criaAnalisador();	

		try {
			TagMagnitudeVector descVector = lta2.createTagMagnitudeVector(descricaoCurta);
			tmv = tmv.add(descVector);
		} catch(IOException e) {
			throw new AnalisadorTextualException(e);
		}

		return tmv;
	}

	/**
	 * Equivale a .analisar(textos, false) (onde textos � um List<String>)<br/>
	 *  
	 * @param textos - Lista de Strings, cada uma representando um texto.
	 * @return Um objeto <code>TagMagnitudeMatrix</code> que representa um vetor com os termos extra�dos do texto e 
	 * seus respectivos pesos.
	 * @throws AnalisadorTextualException Se houver algum erro de IO na leitura do texto passado.
	 *  
	 */

	public TagMagnitudeMatrix analisar(List<String> textos) throws AnalisadorTextualException {
		return analisar(textos, false);
	}
	
	/**
	 * Analisa os textos da lista "textos". Equivale a aplicar .analisar() em cada String da lista.<br/>
	 * Se "aplicaIDF" for verdadeiro, os pesos de cada termo de cada String � diminu�do em func�o<br/>
	 * do n�mero de outras Strings que tamb�m cont�nham tal termo.<br/>
	 * 
	 * O resultado � retornado em uma matriz (objeto do tipo TagMagnitudeMatrix).<br/>
	 * Para conveni�ncia do usu�rio, tal objeto prov� um m�todo .getSimpleRepresentation() que devolve<br/>
	 * um Double[][]. (Veja tamb�m o m�todo .getStemmedTags())<br/>
	 * <code>TagMagnitudeMatrix tmm = new AnalisadorTextual().analisar(listaDeTodasAsNoticias, false);<br/>
	 *       System.out.println("Matriz de pesos: " + tmm); </code>
	 * 
	 * @param textos - Lista de Strings, cada uma representando um texto.
	 * @param aplicaIDF - Boolean indicando se o c�lculo de IDF deve ser levada em conta.
	 * @return Um objeto <code>TagMagnitudeMatrix</code> que representa um vetor com os termos extra�dos do texto e 
	 * seus respectivos pesos.
	 * @throws AnalisadorTextualException Se houver algum erro de IO na leitura do texto passado.
	 *  
	 */
	
	public TagMagnitudeMatrix analisar(List<String> textos, Boolean aplicaIDF) throws AnalisadorTextualException {
		TagMagnitudeMatrix ret = new TagMagnitudeMatrixImpl();
		LinkedList<String> copia = null;

		if (aplicaIDF) {
			copia = new LinkedList<String>();
			copia.addAll(textos);
		}

		for(String texto : textos) {
			TagMagnitudeVector tmv = null; 
			if (aplicaIDF) {
				copia.remove(texto);
				tmv = analisar(texto, copia);
				copia.add(texto);
			} else {
				tmv = analisar(texto);
			}

			ret.addToVectorList(tmv);
		}

		return ret;
	}

	public String createTagCloud(TagMagnitudeVector tmVector) {
		List<TagCloudElement> elements = new ArrayList<TagCloudElement>();
		for (TagMagnitude tm: tmVector.getTagMagnitudes()) {
			TagCloudElement element = new TagCloudElementImpl(tm.getDisplayText(), tm.getMagnitude());
			elements.add(element);
		}
		TagCloud tagCloud = new TagCloudImpl(elements, new LinearFontSizeComputationStrategy(3,"font-size: "));
		
		HTMLTagCloudDecorator decorator = new HTMLTagCloudDecorator();
	    String html = decorator.decorateTagCloud(tagCloud);

		return html;
	}
	
	private void inicializaStopWords() {
		for(String s : BrazilianAnalyzer.BRAZILIAN_STOP_WORDS) {
			stopWords.add(s);
		}
		stopWords.add("para");
		stopWords.add("ex");
		stopWords.add("�");
		stopWords.add("�");
		stopWords.add("at�");
		stopWords.add("aqui");
		stopWords.add("l�");
		stopWords.add("no");
		stopWords.add("pra");
		stopWords.add("pro");
		stopWords.add("n�o");
	}

	/* Setters e getters para as tr�s listas */
	public void setStopWords(List<String> stopWords) {
		this.stopWords = stopWords;
	}

	public List<String> getStopWords() {
		return stopWords;
	}

	public List<String> getListaFrases() {
		return listaFrases;
	}

	public void setListaFrases(List<String> listaFrases) {
		this.listaFrases = listaFrases;
	}

	public Map<String, List<String>> getMapPalavrasSinonimos() {
		return mapPalavrasSinonimos;
	}

	public void setMapPalavrasSinonimos(
			Map<String, List<String>> mapPalavrasSinonimos) {
		this.mapPalavrasSinonimos = mapPalavrasSinonimos;
	}
}
